/*
 * DXArchive format
 *
 * File starts with a header (see DARC_HEAD), followed by file data, and at the end of a file the compressed file table.
 *
 * For DX6, the header is encrypted as well (simple XOR), magic is 'D', 'X', 6, 0.
 * For DX8, the header is NOT encrypted, magic is 'D', 'X', 8, 0.
 *
 * For both, the file table is encrypted and compressed. Once uncompressed, it contains a name table, a file table and a
 * directory table. Using this three a directory-tree is described (does not use full paths in names, rather there's a
 * parent directory pointer). Filenames might use multiple encodings in DX8, but they must have an UTF-8 file name as well.
 *
 * Decryption for DX6 is super easy, no need to know the encryption key, the xor mask can be deducted from the header, and
 * the same 12 bytes long mask is used for every file. On the other hand DX8 uses 7 bytes long xor mask (see KeyCreate() how a
 * string is converted into this), but for files there's a different xor mask than for the file table. There the string goes
 * like: encryption key string + file name + directory name + parent directory name + parent parent directory name... This
 * reversed path string is passed to KeyCreate() to get a file specific 7 bytes long xor mask. Therefore even if we had deducted
 * the file table's xor mask, we would still need the encryption key string, which is different in WolfRPG versions. See a
 * list below in DX8_keys[] array.
 *
 * Notes for those who are familiar with [DxLib](http://dxlib.o.oo7.jp/index.html)'s DXArchive.cpp and DXArchive6.cpp:
 * - all WIN32 API calls replaced with OS-agnostic counterparts
 * - Huffman_Decode() fixed buffer overflow (out of bounds read on Weight[])
 * - CharCode replaced with common code for ASCII, UTF-8 and SHIFTJIS (CP932). Others might work, but not supported for now
 * - common handling of both DXArchive and DXArchive6 (eg. DARC_HEAD / DARC_HEAD_VER6 -> DARC_HEAD, a single struct for both)
 * - DXArchive::DecodeArchive() -> getfileindex() + freefileindex() (just fills up idx[] from file table)
 * - DXArchive::DirectoryDecode() -> DirectoryDecode() + FileDecode() (separate directory walk and file extraction function)
 * - DXArchive::GetOriginalFileName() -> merged into DirectoryDecode() for proper path string construction and limit check
 *
 * Game.wolf:
 * Only exists in WolfRPG PRO games. This is not a DXLib archive, rather it stores the encryption key for the DXLib archives.
 *
 * BasicData/Game.dat:
 * Starts with magic bytes (see dat_magic[]). This is followed by the size of settings on 4 bytes. The first byte of the settings
 * is the tile size. The settings block is followed by the version on 4 bytes (usually 9). Then comes the length of the game's
 * title on 4 bytes, and the game's title, a zero terminated UTF-8 string. Haven't figured out the rest.
 * In WolfRPG PRO this file is additionally encrypted and has a variable length header.
 *
 * BasicData/TileSet.dat:
 * Starts with the same magic bytes as Game.dat... Then version on 1 byte (either 209 or 210), followed by the number of tile sets
 * on 4 bytes. Then that many blocks follows: 4 bytes tile set's name length, zero terminated tile set name (this is SHIFTJIS
 * encoded for version 209). Then multiple 4 bytes file name length and name of the sprite sheet, zero terminated UTF-8 (usually
 * MapChip/something.png). This sheet list is terminated by a 0xff byte (for version 209 there are usually 16 sprite sheet names,
 * for version 210 there are 32, but doesn't matter, both terminated by 0xff). The first file name is for the base tile set, the
 * rest is for the auto tiles. Then number of tags on 4 bytes, followed by the tags, each one byte. This is also terminated by a
 * 0xff for some reason. Then another length on 4 bytes, and that many tile flags each 4 bytes. Then the next tile set follows.
 * In WolfRPG PRO this file is additionally encrypted and has a variable length header.
 *
 * MPS format:
 * Starts with a header (see mps_magic[]), and format version on one byte (either 0x65 or 0x66). Then length of the name and zero
 * terminated name. If version is 0x65, then name is SHIFTJIS encoded, not that it matters because you cannot change this from
 * the editor (the editor uses the file name), so just ignore. Then tileset id on 4 bytes, width on 4 bytes, height on 4 bytes,
 * number of event records on 4 bytes. After this comes 3 layers, each width x height and each tile is stored on 4 bytes. But
 * watch out, this is stored as row major, so to get the tile at (x,y) you wouldn't do y * w + x as you normally would, instead
 * x * h + y. If tile is smaller than 100000, then the sprite is taken from the base tile sheet normally. Otherwise the tile
 * is taken from x / 10000 = 0 first sheet, x / 100000 = 1 first sheet (but not drawn), x / 100000 = 2 second sheet, x / 100000 = 3
 * third sheet etc. Then top_left = tile % 10000 / 1000, top_right = tile % 1000 / 100, bottom_left = tile % 100 / 10,
 * bottom_right = tile % 10 and these are used to get the coordinate on that sheet.
 */

#include "main.h"

#define FILE_ATTRIBUTE_DIRECTORY 0x10
#define MIN_COMPRESS            (4)
#define CHARCODEFORMAT_SHIFTJIS (932)
#define CHARCODEFORMAT_UTF8     (65001)
#define CHARCODEFORMAT_ASCII    (32765)
#define DXA_FLAG_NO_KEY         (0x00000001)
#define DXA_FLAG_NO_HEAD_PRESS  (0x00000002)
#define DXA_KEY_STRING_LENGTH   (63)
#define DXA_KEY_STRING_MAXLENGTH (2048)
#define DXA_KEY_BYTES           (7)

/* add known DX8 encryption keys here (not needed for DX6 nor for WolfRPG PRO) */
static uint8_t DX8_keys[][DXA_KEY_STRING_LENGTH + 1] = {
    /* WolfRPG PRO (placeholder for autodetected key) */
    { 0 },
    /* WolfRPG 2.281 */
    { 'W','L','F','R','P','r','O','!','p','(',';','s','5','(','(','8','P','@','(','(','U','F','W','l','u','$','#','5','(','=',0 },
    /* WolfRPG 3.10 */
    { 0x0F, 0x53, 0xE1, 0x3E, 0x8E, 0xB5, 0x41, 0x91, 0x52, 0x16, 0x55, 0xAE, 0x34, 0xC9, 0x8F, 0x79, 0x59, 0x2F, 0x59, 0x6B,
      0x95, 0x19, 0x9B, 0x1B, 0x35, 0x9A, 0x2F, 0xDE, 0xC9, 0x7C, 0x12, 0x96, 0xC3, 0x14, 0xB5, 0x0F, 0x53, 0xE1, 0x3E, 0x8E, 0 },
    /* WolfRPG 3.14 */
    { 0x31, 0xF9, 0x01, 0x36, 0xA3, 0xE3, 0x8D, 0x3C, 0x7B, 0xC3, 0x7D, 0x25, 0xAD, 0x63, 0x28, 0x19, 0x1B, 0xF7, 0x8E, 0x6C,
      0xC4, 0xE5, 0xE2, 0x76, 0x82, 0xEA, 0x4F, 0xED, 0x61, 0xDA, 0xE0, 0x44, 0x5B, 0xB6, 0x46, 0x3B, 0x06, 0xD5, 0xCE, 0xB6,
      0x78, 0x58, 0xD0, 0x7C, 0x82, 0 },
    /* WolfRPG 3.173 */
    { 0x31, 0xF9, 0x01, 0x36, 0xA3, 0xE3, 0x8D, 0x3C, 0x7B, 0xC3, 0x7D, 0x25, 0xAD, 0x63, 0x28, 0x19, 0x1B, 0xF7, 0x8E, 0x6C,
      0xC4, 0xE5, 0xE2, 0x76, 0x82, 0xEA, 0x4F, 0xED, 0x61, 0xDA, 0xE0, 0x44, 0x5B, 0xB6, 0x46, 0x3B, 0x06, 0xD5, 0xCE, 0xB6,
      0x78, 0x58, 0xD0, 0x7C, 0x82, 0 },
    /* WolfRPG 3.31 */
    { 0xCA, 0x08, 0x4C, 0x5D, 0x17, 0x0D, 0xDA, 0xA1, 0xD7, 0x27, 0xC8, 0x41, 0x54, 0x38, 0x82, 0x32, 0x54, 0xB7, 0xF9, 0x46,
      0x8E, 0x13, 0x6B, 0xCA, 0xD0, 0x5C, 0x95, 0x95, 0xE2, 0xDC, 0x03, 0x53, 0x60, 0x9B, 0x4A, 0x38, 0x17, 0xF3, 0x69, 0x59,
      0xA4, 0xC7, 0x9A, 0x43, 0x63, 0xE6, 0x54, 0xAF, 0xDB, 0xBB, 0x43, 0x58, 0 }
};

/* known magic keys */
static uint8_t dat_magic[] = { 0x00, 0x57, 0x00, 0x00, 0x4f, 0x4c },
    mps_magic[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x4F, 0x4C, 0x46, 0x4D, 0x00 };

/* file index */
typedef struct {
    char name[NAMELEN];
    uint64_t offs;
    uint64_t size;
    uint64_t comp;  /* compressed size is non-zero if LZ compressed (both DX6 and DX8) */
    uint64_t huff;  /* huffmann size is non-zero if Huffmann compressed too (DX8 only) */
    uint8_t key[15];/* file xor mask (DX8 only, for DX6 use Key[]) */
    uint8_t isdir;
} index_t;
static index_t *idx = NULL;
static int numidx = 0;
static uint64_t total, current;

/******** ported from DXLib starts ********/
typedef struct {
    uint16_t Head ;
    uint16_t Version ;
    uint32_t HeadSize ;
    uint64_t DataStartAddress ;
    uint64_t FileNameTableStartAddress ;
    uint64_t FileTableStartAddress ;
    uint64_t DirectoryTableStartAddress ;
    uint32_t CharCodeFormat ;
    /* only in DX8 */
    uint32_t Flags ;
    uint8_t  HuffmanEncodeKB ;
    uint8_t  Reserve[ 15 ] ;
} __attribute__((packed)) DARC_HEAD;

typedef struct {
    uint64_t NameAddress ;
    uint64_t Attributes ;
    uint64_t Create ;
    uint64_t LastAccess ;
    uint64_t LastWrite ;
    uint64_t DataAddress ;
    uint64_t DataSize ;
    uint64_t PressDataSize ;
    /* only in DX8 */
    uint64_t HuffPressDataSize ;
} __attribute__((packed)) DARC_FILEHEAD;

typedef struct {
    uint64_t DirectoryAddress ;
    uint64_t ParentDirectoryAddress ;
    uint64_t FileHeadNum ;
    uint64_t FileHeadAddress ;
} __attribute__((packed)) DARC_DIRECTORY;

typedef struct {
    uint8_t *Buffer ;
    uint64_t Bytes ;
    uint8_t Bits ;
} BIT_STREAM;

typedef struct {
    uint64_t Weight ;
    int BitNum ;
    unsigned char BitArray[32] ;
    int Index ;
    int ParentNode ;
    int ChildNode[2] ;
} HUFFMAN_NODE;

static DARC_HEAD Head;
static uint8_t detected = 0, Key[12], KeyString[DXA_KEY_STRING_LENGTH + 1], fullPath[NAMELEN];
static uint32_t KeyLen = 0, KeyStringBytes = 0;

static void BitStream_Init( BIT_STREAM *BitStream, void *Buffer )
{
    BitStream->Buffer = ( uint8_t * )Buffer ;
    BitStream->Bytes = 0 ;
    BitStream->Bits = 0 ;
}

static uint64_t BitStream_Read( BIT_STREAM *BitStream, uint8_t BitNum )
{
    uint64_t Result = 0 ;
    uint32_t i ;
    for( i = 0 ; i < BitNum ; i ++ )
    {
        Result |= ( ( uint64_t )( ( BitStream->Buffer[ BitStream->Bytes ] >> ( 7 - BitStream->Bits ) ) & 1 ) ) << ( BitNum - 1 - i ) ;
        BitStream->Bits ++ ;
        if( BitStream->Bits == 8 )
        {
            BitStream->Bytes ++ ;
            BitStream->Bits = 0 ;
        }
    }
    return Result ;
}

static uint64_t BitStream_GetBytes( BIT_STREAM *BitStream )
{
    return BitStream->Bytes + ( BitStream->Bits != 0 ? 1 : 0 ) ;
}

static uint64_t Huffman_Decode( void *Press, uint64_t maxlen, void *Dest )
{
    HUFFMAN_NODE Node[256 + 255] ;
    uint64_t PressSizeCounter, DestSizeCounter, DestSize ;
    unsigned char *PressPoint, *DestPoint ;
    uint64_t OriginalSize = 0;
    uint64_t PressSize ;
    uint64_t HeadSize ;
    uint16_t Weight[ 256 + 255 ] ;
    BIT_STREAM BitStream ;
    uint8_t BitNum ;
    uint8_t Minus ;
    uint16_t SaveData ;
    uint16_t BitArray01 ;
    int b ;
    int NodeIndex, MinNode1, MinNode2 ;
    int NodeNum, DataNum ;
    unsigned char TempBitArray[32] ;
    int TempBitIndex, TempBitCount, BitIndex, BitCount ;
    uint16_t BitMask[ 9 ] ;
    unsigned char *PressData ;
    int PressBitCounter, PressBitData, Index;
    int NodeIndexTable[ 512 ] ;
    int i, j ;

    PressPoint = ( unsigned char * )Press ;
    DestPoint = ( unsigned char * )Dest ;

    BitStream_Init( &BitStream, PressPoint ) ;

    b = BitStream_Read( &BitStream, 6 ) + 1;
    if(b < 4 || b > 32) return 0;
    OriginalSize = BitStream_Read( &BitStream, ( uint8_t )( b ) );
    b = BitStream_Read( &BitStream, 6 ) + 1;
    if(b < 4 || b > 32) return 0;
    PressSize    = BitStream_Read( &BitStream, ( uint8_t )( b ) ) ;
    if(OriginalSize < PressSize || OriginalSize > PressSize * 4 || PressSize > 1024*1024) return 0;
    BitNum      = ( uint8_t )( BitStream_Read( &BitStream, 3 ) + 1 ) * 2 ;
    Minus       = ( uint8_t )BitStream_Read( &BitStream, 1 ) ;
    SaveData    = ( uint16_t )BitStream_Read( &BitStream, BitNum ) ;
    memset(Weight, 0, sizeof(Weight));
    Weight[ 0 ] = SaveData ;
    for( i = 1 ; i < 256 ; i ++ )
    {
        BitNum      = ( uint8_t )( BitStream_Read( &BitStream, 3 ) + 1 ) * 2 ;
        Minus       = ( uint8_t )BitStream_Read( &BitStream, 1 ) ;
        SaveData    = ( uint16_t )BitStream_Read( &BitStream, BitNum ) ;
        Weight[ i ] = Minus == 1 ? Weight[ i - 1 ] - SaveData : Weight[ i - 1 ] + SaveData ;
        if(BitStream.Bytes >= maxlen) return 0;
    }

    HeadSize = BitStream_GetBytes( &BitStream ) ;

    if( Dest == NULL )
        return OriginalSize ;

    DestSize = OriginalSize ;

    for( i = 0 ; i < 256 + 255 ; i ++ )
    {
        Node[i].Weight = Weight[i] ;
        Node[i].ChildNode[0] = -1 ;
        Node[i].ChildNode[1] = -1 ;
        Node[i].ParentNode = -1 ;
    }

    DataNum = 256;
    NodeNum = 256;
    while( DataNum > 1 )
    {
            MinNode1 = -1 ;
            MinNode2 = -1 ;
            NodeIndex = 0 ;
            for( i = 0 ; i < DataNum ; NodeIndex ++ )
            {
                if( Node[NodeIndex].ParentNode != -1 ) continue ;

                i ++ ;

                if( MinNode1 == -1 || Node[MinNode1].Weight > Node[NodeIndex].Weight )
                {
                    MinNode2 = MinNode1 ;
                    MinNode1 = NodeIndex ;
                }
                else
                {
                    if( MinNode2 == -1 || Node[MinNode2].Weight > Node[NodeIndex].Weight )
                    {
                        MinNode2 = NodeIndex ;
                    }
                }
            }
            Node[NodeNum].ParentNode = -1 ;
            Node[NodeNum].Weight = Node[MinNode1].Weight + Node[MinNode2].Weight ;
            Node[NodeNum].ChildNode[0] = MinNode1 ;
            Node[NodeNum].ChildNode[1] = MinNode2 ;
            Node[MinNode1].Index = 0 ;
            Node[MinNode2].Index = 1 ;
            Node[MinNode1].ParentNode = NodeNum ;
            Node[MinNode2].ParentNode = NodeNum ;
            NodeNum ++ ;
            DataNum -- ;
    }

    for( i = 0 ; i < 256 + 254 ; i ++ )
    {
            Node[i].BitNum = 0 ;

            TempBitIndex = 0 ;
            TempBitCount = 0 ;
            TempBitArray[TempBitIndex] = 0 ;

            for( NodeIndex = ( int )i ; Node[NodeIndex].ParentNode != -1 ; NodeIndex = Node[NodeIndex].ParentNode )
            {
                if( TempBitCount == 8 )
                {
                    TempBitCount = 0 ;
                    TempBitIndex ++ ;
                    TempBitArray[TempBitIndex] = 0 ;
                }
                TempBitArray[TempBitIndex] <<= 1 ;
                TempBitArray[TempBitIndex] |= (unsigned char)Node[NodeIndex].Index ;
                TempBitCount ++ ;
                Node[i].BitNum ++ ;
            }

            BitCount = 0 ;
            BitIndex = 0 ;
            Node[i].BitArray[BitIndex] = 0 ;
            while( TempBitIndex >= 0 )
            {
                if( BitCount == 8 )
                {
                    BitCount = 0 ;
                    BitIndex ++ ;
                    Node[i].BitArray[BitIndex] = 0 ;
                }
                Node[i].BitArray[BitIndex] |= (unsigned char)( ( TempBitArray[TempBitIndex] & 1 ) << BitCount ) ;
                TempBitArray[TempBitIndex] >>= 1 ;
                TempBitCount -- ;
                if( TempBitCount == 0 )
                {
                    TempBitIndex -- ;
                    TempBitCount = 8 ;
                }
                BitCount ++ ;
            }
    }

    for( i = 0 ; i < 9 ; i ++ )
    {
        BitMask[ i ] = ( uint16_t )( ( 1 << ( i + 1 ) ) - 1 ) ;
    }

    for( i = 0 ; i < 512 ; i ++ )
    {
        NodeIndexTable[ i ] = -1 ;
        for( j = 0 ; j < 256 + 254 ; j ++ )
        {
            if( Node[ j ].BitNum > 9 )
            {
                continue ;
            }
            BitArray01 = ( uint16_t )Node[ j ].BitArray[ 0 ] | ( Node[ j ].BitArray[ 1 ] << 8 ) ;
            if( ( i & BitMask[ Node[ j ].BitNum - 1 ] ) == ( BitArray01 & BitMask[ Node[ j ].BitNum - 1 ] ) )
            {
                NodeIndexTable[ i ] = j ;
                break ;
            }
        }
    }
    maxlen -= HeadSize;
    PressData = PressPoint + HeadSize ;
    DestSizeCounter = 0 ;
    PressSizeCounter = 0 ;
    PressBitCounter = 0 ;
    PressBitData = PressData[PressSizeCounter] ;

    for( DestSizeCounter = 0 ; DestSizeCounter < DestSize ; DestSizeCounter ++ )
    {
        if(PressSizeCounter + 1 >= maxlen) return 0;
        if( DestSizeCounter >= DestSize - 17 )
        {
            NodeIndex = 510 ;
        }
        else
        {
            if( PressBitCounter == 8 )
            {
                PressSizeCounter ++ ;
                PressBitData = PressData[PressSizeCounter] ;
                PressBitCounter = 0 ;
            }
            PressBitData = ( PressBitData | ( PressData[ PressSizeCounter + 1 ] << ( 8 - PressBitCounter ) ) ) & 0x1ff ;
            NodeIndex = NodeIndexTable[ PressBitData ] ;
            PressBitCounter += Node[ NodeIndex ].BitNum ;
            if( PressBitCounter >= 16 )
            {
                PressSizeCounter += 2 ;
                PressBitCounter -= 16 ;
                PressBitData = PressData[PressSizeCounter] >> PressBitCounter ;
            }
            else
            if( PressBitCounter >= 8 )
            {
                PressSizeCounter ++ ;
                PressBitCounter -= 8 ;
                PressBitData = PressData[PressSizeCounter] >> PressBitCounter ;
            }
            else
            {
                PressBitData >>= Node[ NodeIndex ].BitNum ;
            }
        }
        while( NodeIndex > 255 )
        {
            if( PressBitCounter == 8 )
            {
                PressSizeCounter ++ ;
                if(PressSizeCounter >= maxlen) return 0;
                PressBitData = PressData[PressSizeCounter] ;
                PressBitCounter = 0 ;
            }
            Index = PressBitData & 1 ;
            PressBitData >>= 1 ;
            PressBitCounter ++ ;
            NodeIndex = Node[NodeIndex].ChildNode[Index] ;
        }
        DestPoint[DestSizeCounter] = (unsigned char)NodeIndex ;
    }
    return OriginalSize ;
}

static int Decode( void *Src, uint32_t maxlen, void *Dest, uint32_t dstlen )
{
    uint32_t srcsize, destsize, code, indexsize, keycode, conbo, index = 0, num ;
    uint8_t *srcp, *destp, *dp, *sp, *spend = (uint8_t*)Src + maxlen;

    destp = (uint8_t *)Dest ;
    srcp  = (uint8_t *)Src ;
    destsize = *((uint32_t *)&srcp[0]) ;
    srcsize = *((uint32_t *)&srcp[4]) - 9 ;
    keycode = srcp[8] ;
    if( Dest == NULL )
        return destsize ;

    sp  = srcp + 9 ;
    dp  = destp ;
    while( srcsize && sp < spend && (uintptr_t)dp - (uintptr_t)destp < dstlen )
    {
        if( sp[0] != keycode )
        {
            *dp = *sp ;
            dp      ++ ;
            sp      ++ ;
            srcsize -- ;
            continue ;
        }
        if( sp[1] == keycode )
        {
            *dp = (uint8_t)keycode ;
            dp      ++ ;
            sp      += 2 ;
            srcsize -= 2 ;

            continue ;
        }
        code = sp[1] ;
        if( code > keycode ) code -- ;
        sp      += 2 ;
        srcsize -= 2 ;
        conbo = code >> 3 ;
        if( code & ( 0x1 << 2 ) )
        {
            conbo |= *sp << 5 ;
            sp      ++ ;
            srcsize -- ;
        }
        conbo += MIN_COMPRESS ;
        indexsize = code & 0x3 ;
        switch( indexsize )
        {
        case 0 :
            index = *sp ;
            sp      ++ ;
            srcsize -- ;
            break ;
        case 1 :
            index = *((uint16_t *)sp) ;
            sp      += 2 ;
            srcsize -= 2 ;
            break ;
        case 2 :
            index = *((uint16_t *)sp) | ( sp[2] << 16 ) ;
            sp      += 3 ;
            srcsize -= 3 ;
            break ;
        }
        index ++ ;
        if( index < conbo )
        {
            num  = index ;
            while( conbo > num )
            {
                memcpy( dp, dp - num, num ) ;
                dp    += num ;
                conbo -= num ;
                num   += num ;
            }
            if( conbo != 0 )
            {
                memcpy( dp, dp - num, conbo ) ;
                dp += conbo ;
            }
        }
        else
        {
            memcpy( dp, dp - index, conbo ) ;
            dp += conbo ;
        }
    }
    return (int)destsize ;
}

static void KeyCreate( const uint8_t *Source, size_t SourceBytes, uint8_t *Key )
{
    char WorkBuffer[ DXA_KEY_STRING_MAXLENGTH + 2 ] = { 0 };
    uint32_t i, j, CRC32_0, CRC32_1 ;

    j = 0 ;
    for( i = 0 ; i < SourceBytes ; i += 2, j++ )
        WorkBuffer[ j ] = Source[ i ] ;
    CRC32_0 = crc32( WorkBuffer, j ) ;

    j = 0 ;
    for( i = 1 ; i < SourceBytes ; i += 2, j++ )
        WorkBuffer[ j ] = Source[ i ] ;
    CRC32_1 = crc32( WorkBuffer, j ) ;

    Key[ 0 ] = ( uint8_t )( CRC32_0 >>  0 ) ;
    Key[ 1 ] = ( uint8_t )( CRC32_0 >>  8 ) ;
    Key[ 2 ] = ( uint8_t )( CRC32_0 >> 16 ) ;
    Key[ 3 ] = ( uint8_t )( CRC32_0 >> 24 ) ;
    Key[ 4 ] = ( uint8_t )( CRC32_1 >>  0 ) ;
    Key[ 5 ] = ( uint8_t )( CRC32_1 >>  8 ) ;
    Key[ 6 ] = ( uint8_t )( CRC32_1 >> 16 ) ;
}

static size_t CreateKeyFileString( DARC_DIRECTORY *Directory, DARC_FILEHEAD *FileHead, uint8_t *FileTable, uint8_t *DirectoryTable, uint8_t *NameTable, uint8_t *FileString )
{
    char *str;
    size_t StartAddr ;

    memset(FileString, 0, DXA_KEY_STRING_MAXLENGTH);
    if( KeyStringBytes != 0 ) {
        memcpy( FileString, KeyString, KeyStringBytes ) ;
        FileString[ KeyStringBytes ] = '\0' ;
        StartAddr = KeyStringBytes ;
    } else {
        FileString[ 0 ] = '\0' ;
        StartAddr = 0 ;
    }
    for(str = ( char * )( NameTable + FileHead->NameAddress + 4 ); StartAddr < DXA_KEY_STRING_MAXLENGTH - 1 && *str; str++, StartAddr++)
        FileString[StartAddr] = *str;
    if( Directory->ParentDirectoryAddress != 0xffffffffffffffff ) {
        do {
            for(str = ( char * )( NameTable + ( ( DARC_FILEHEAD * )( FileTable + Directory->DirectoryAddress ) )->NameAddress + 4 ); StartAddr < DXA_KEY_STRING_MAXLENGTH - 1 && *str; str++, StartAddr++)
                FileString[StartAddr] = *str;
            Directory = ( DARC_DIRECTORY * )( DirectoryTable + Directory->ParentDirectoryAddress ) ;
        } while( StartAddr < DXA_KEY_STRING_MAXLENGTH - 1 && Directory->ParentDirectoryAddress != 0xffffffffffffffff ) ;
    }

    return StartAddr;
}

static void KeyConv( void *Data, int64_t Size, int64_t Position, unsigned char *Key )
{
    int64_t i, j;

    if( Key == NULL || !KeyLen )
        return ;

    j = Position % (int64_t)KeyLen;
    for( i = 0 ; i < Size ; i ++ )
    {
        ((uint8_t *)Data)[i] ^= Key[j] ;

        j ++ ;
        if( j == KeyLen ) j = 0 ;
    }
}

static void KeyConvFileRead( void *Data, int64_t Size, void *fp, unsigned char *Key, int64_t Position )
{
    fileread(fp, Data, Size);
    if( Key != NULL && KeyLen )
        KeyConv( Data, Size, Position, Key ) ;
}

static void DirectoryDecode( uint8_t *NameP, uint8_t *DirP, uint8_t *FileP, DARC_DIRECTORY *Dir, void *f, int pathlen )
{
    static uint8_t KeyStringBuffer[DXA_KEY_STRING_MAXLENGTH];
    uint32_t i, len, KeyStringBufferBytes, FileHeadSize = sizeof(DARC_FILEHEAD) - (Head.Version < 8 ? sizeof(uint64_t) : 0);
    DARC_FILEHEAD *File = ( DARC_FILEHEAD * )( FileP + Dir->FileHeadAddress ) ;
    uint8_t *pName, *FileNameTable;
    int j;

    fullPath[pathlen++] = '/';
    for( i = 0 ; !quit && i < Dir->FileHeadNum ; i ++, File = (DARC_FILEHEAD *)( (uint8_t *)File + FileHeadSize ) ) {
        FileNameTable = NameP + File->NameAddress;
        pName = (FileNameTable + *((uint16_t *)&FileNameTable[0]) * 4 + 4);
        if(!validutf8((char*)pName)) {
            shiftjis2utf8((char*)pName, (char*)fullPath + pathlen, (int)sizeof(fullPath) - pathlen - 1);
            len = strlen((char*)fullPath + pathlen);
        } else {
            for(len = 0; pName[len] && pathlen + len + 1 < (int)sizeof(fullPath); len++)
                fullPath[pathlen + len] = pName[len];
            fullPath[pathlen + len] = 0;
        }
        if( File->Attributes & FILE_ATTRIBUTE_DIRECTORY )
        {
            if(pathlen + len + 2 < (int)sizeof(fullPath))
                DirectoryDecode( NameP, DirP, FileP, ( DARC_DIRECTORY * )( DirP + File->DataAddress ), f, pathlen + len ) ;
        } else
        if( File->DataSize ) {
            j = numidx++;
            if(!(idx = (index_t*)realloc(idx, numidx * sizeof(index_t)))) {
                debug("unable to allocate memory, too many table entries %u", numidx);
                numidx = 0; break;
            }
            memset(&idx[j], 0, sizeof(index_t));
            memcpy(idx[j].name, fullPath, pathlen + len);
            idx[j].isdir = !!(File->Attributes & FILE_ATTRIBUTE_DIRECTORY);
            idx[j].offs = File->DataAddress;
            idx[j].size = File->DataSize;
            idx[j].comp = File->PressDataSize;
            if(Head.Version >= 8) idx[j].huff = File->HuffPressDataSize;
            KeyStringBufferBytes = CreateKeyFileString( Dir, File, FileP, DirP, NameP, ( uint8_t * )KeyStringBuffer ) ;
            KeyCreate( KeyStringBuffer, KeyStringBufferBytes, idx[j].key ) ;
        }
    }
}

static uint8_t *FileDecode(void *f, index_t *idx)
{
    uint64_t l = (uint64_t)Head.HuffmanEncodeKB * 1024;
    uint8_t *buf, *temp = NULL, *xormask;

    if(quit || !f || !idx || !idx->name[0] || !idx->size || !Head.DataStartAddress) return NULL;
    if(verbose > 1) debug("file in archive '%s' (%lu bytes)", idx->name, idx->size);
    xormask = !KeyLen ? NULL : (Head.Version < 8 ? Key : idx->key);
    if(!(buf = (uint8_t*)malloc(idx->size))) {
        engine_features |= EF_NOMEM;
        debug("unable to allocate memory for '%s' (%lu bytes)", idx->name, idx->size);
        return NULL;
    }
    l = idx->size + (idx->comp != 0xffffffffffffffff ? idx->comp : 0) +
        (Head.Version >= 8 && idx->huff != 0xffffffffffffffff ? idx->huff : 0);
    if(l > idx->size && !(temp = (uint8_t*)malloc(l + 256))) {
        free(buf);
        engine_features |= EF_NOMEM;
        debug("unable to allocate memory for '%s' (%lu bytes)", idx->name, l);
        return NULL;
    }
    l = (uint64_t)Head.HuffmanEncodeKB * 1024;
    fileseek(f, Head.DataStartAddress + idx->offs);
    if( idx->comp && idx->comp != 0xffffffffffffffff ) {
        if( Head.Version >= 8 && idx->huff && idx->huff != 0xffffffffffffffff ) {
            KeyConvFileRead( temp, idx->huff, f, xormask, idx->size ) ;
            Huffman_Decode( temp, idx->huff, temp + idx->huff ) ;
            if( Head.HuffmanEncodeKB != 0xff && idx->comp > l * 2 ) {
                memmove(temp + idx->huff + idx->comp - l, temp + idx->huff + l, l);
                KeyConvFileRead( temp + idx->huff + l, idx->comp - l * 2, f, xormask, idx->size + idx->huff ) ;
            }
            Decode( temp + idx->huff, idx->comp, temp + idx->huff + idx->comp, idx->size ) ;
            memcpy(buf, temp + idx->huff + idx->comp, idx->size ) ;
        } else {
            KeyConvFileRead( temp, idx->comp, f, xormask, idx->size ) ;
            Decode( temp, idx->comp, temp + idx->comp, idx->size ) ;
            memcpy(buf, temp + idx->comp, idx->size ) ;
        }
    } else {
        if( Head.Version >= 8 && idx->huff && idx->huff != 0xffffffffffffffff ) {
            KeyConvFileRead( temp, idx->huff, f, xormask, idx->size ) ;
            Huffman_Decode( temp, idx->huff, temp + idx->huff ) ;
            if( Head.HuffmanEncodeKB != 0xff && idx->size > l * 2 ) {
                memmove(temp + idx->huff + idx->size - l, temp + idx->huff + l, l);
                KeyConvFileRead( temp + idx->huff + l, idx->size - l * 2, f, xormask, idx->size + idx->huff ) ;
            }
            memcpy(buf, temp + idx->huff, idx->size ) ;
        } else {
            KeyConvFileRead( buf, idx->size, f, xormask, idx->size ) ;
        }
    }
    if(temp) free(temp);
    return buf;
}
/******** ported from DXLib ends ********/

/**
 * Free file index
 */
static void freefileindex(void)
{
    if(idx) { free(idx); idx = NULL; }
    numidx = 0;
}

/**
 * Get file index (parse DXLib archive)
 */
static int getfileindex(char *path, void *f, uint64_t size, void *KeyString_)
{
    uint32_t i;
    uint8_t *buf = (uint8_t*)&Head, NoKey = 0;
    uint8_t *HeadBuffer = NULL;
    void *HuffHeadBuffer = NULL;
    uint64_t HuffHeadSize ;
    void *LzHeadBuffer = NULL;
    uint64_t LzHeadSize ;
    char *ext;

    KeyStringBytes = 0; memset(KeyString, 0, sizeof(KeyString));
    freefileindex();
    if(!f || size < 64) return 0;

    /* parse header */
    fileseek(f, 0);
    fileread(f, &Head, sizeof(DARC_HEAD));

    if(buf[0] == 'D' && buf[1] == 'X' && buf[2] == 8) {
        /* DX8, no unencrypted variant, but header isn't encrypted */
        if(!KeyString_) KeyString_ = DX8_keys[0];
        KeyStringBytes = strlen((char*)KeyString_);
        if(KeyStringBytes > DXA_KEY_STRING_LENGTH)
            KeyStringBytes = DXA_KEY_STRING_LENGTH;
        memcpy(KeyString, KeyString_, KeyStringBytes);
        KeyString[KeyStringBytes] = 0;
        KeyCreate( KeyString, KeyStringBytes, Key ) ;
        KeyLen = DXA_KEY_BYTES;
        if(Head.CharCodeFormat != CHARCODEFORMAT_UTF8 && Head.CharCodeFormat != CHARCODEFORMAT_ASCII &&
            Head.CharCodeFormat != CHARCODEFORMAT_SHIFTJIS) {
            debug("unsupported DX CharCodeFormat %d", Head.CharCodeFormat);
            goto ERR;
        }
    } else
    if(buf[0] == 'D' && buf[1] == 'X' && buf[2] == 6) {
        /* DX6 without encryption */
        KeyLen = 0; memset(Key, 0, sizeof(Key));
    } else
    if(size < 0x100000000 && (buf[0] ^ buf[0xc]) == 'D' && (buf[1] ^ buf[0xd]) == 'X' && (buf[2] ^ buf[0xe]) == 6) {
        /* DX6 with encryption, detect the key from the header */
        KeyLen = 12; memcpy(Key, buf + 0xc, 4); memcpy(Key + 4, buf + 0x1c, 4); memcpy(Key + 8, buf + 0x2c, 4);
        for(i = 0; i < sizeof(DARC_HEAD); i++) buf[i] ^= Key[i % KeyLen];
    } else {
        /* unknown variant */
        debug("Unsupported DX version");
ERR:    KeyLen = 0; memset(&Head, 0, sizeof(DARC_HEAD));
        if(HeadBuffer) free(HeadBuffer);
        if(HuffHeadBuffer) free(HuffHeadBuffer);
        if(LzHeadBuffer) free(LzHeadBuffer);
        return 0;
    }
    /* allright, now we have (regardless to archive format version):
     * Head: decrypted DXA header
     * KeyString: zero terminated encryption key (always zero for DX6)
     * KeyStringBytes: length of KeyString (always zero for DX6)
     * Key: file table xor mask (set for both DX6 and DX8)
     * KeyLen: xor mask size (12 for DX6 and 7 for DX8)
     * For DX6 we just need Key, but for DX8 we'll need KeyString 'cos we have to generate a new xor key for each file
     */

    /* DXArchive::DecodeArchive */
    NoKey = Head.Version < 8 ? 0 : ( Head.Flags & DXA_FLAG_NO_KEY ) != 0 ;
    HeadBuffer = ( uint8_t * )malloc( ( size_t )Head.HeadSize ) ;
    if( HeadBuffer == NULL ) { engine_features |= EF_NOMEM; debug("memory allocation error"); goto ERR ; }

    fileseek(f, Head.FileNameTableStartAddress);
    if( ( Head.Version < 8 || Head.Flags & DXA_FLAG_NO_HEAD_PRESS ) != 0 ) {
        KeyConvFileRead( HeadBuffer, Head.HeadSize, f, NoKey ? NULL : Key, 0 ) ;
    } else {
        HuffHeadSize = size - Head.FileNameTableStartAddress ;

        HuffHeadBuffer = (uint8_t*)malloc( ( size_t )HuffHeadSize ) ;
        if( HuffHeadBuffer == NULL ) { engine_features |= EF_NOMEM; debug("memory allocation error"); goto ERR ; }
        KeyConvFileRead( HuffHeadBuffer, HuffHeadSize, f, NoKey ? NULL : Key, 0 ) ;
        if(!(LzHeadSize = Huffman_Decode( HuffHeadBuffer, HuffHeadSize, NULL ))) goto ERR;

        LzHeadBuffer = (uint8_t*)malloc( ( size_t )LzHeadSize + 256 ) ;
        if( LzHeadBuffer == NULL ) { engine_features |= EF_NOMEM; debug("memory allocation error"); goto ERR ; }
        memset(LzHeadBuffer, 0, LzHeadSize + 256);
        if(!(Huffman_Decode( HuffHeadBuffer, HuffHeadSize, LzHeadBuffer )) ||
           (uint32_t)Decode( LzHeadBuffer, LzHeadSize, HeadBuffer, Head.HeadSize ) != Head.HeadSize) goto ERR;

        free( HuffHeadBuffer ) ; HuffHeadBuffer = NULL;
        free( LzHeadBuffer ) ; LzHeadBuffer = NULL;
    }
    if((ext = strrchr(path, '/'))) ext++; else
    if((ext = strrchr(path, '\\'))) ext++; else ext = path;
    if(!memcmp(ext, "Data", 4) || !memcmp(ext, "data", 4)) ext += 4;
    strncpy((char*)fullPath, ext, NAMELEN - 1);
    if((ext = strrchr((char*)fullPath, '.'))) *ext = 0;
    DirectoryDecode( HeadBuffer, HeadBuffer + Head.DirectoryTableStartAddress, HeadBuffer + Head.FileTableStartAddress,
        (DARC_DIRECTORY *)(HeadBuffer + Head.DirectoryTableStartAddress), f, strlen((char*)fullPath) );

    free(HeadBuffer);
    return numidx;
}

/**
 * Return true if buffer is a DXLib archive
 */
static int dxlib_detect(uint8_t *buf, uint64_t size)
{
    return (buf[0] == 'D' && buf[1] == 'X' && (buf[2] == 6 || buf[2] == 8) && !buf[3]) ||
        (size < 0x100000000 && (buf[0] ^ buf[0xc]) == 'D' && (buf[1] ^ buf[0xd]) == 'X' && (buf[2] ^ buf[0xe]) == 6);
}

#define MAXAUTOTILES 32
static int tilesize = 0, numtilesets = 0, *tilesets = NULL, lcg;
static char *tsnames = NULL;
extern map_t *maps;

/**
 * Pseudo random that WolfRRG PRO uses
 */
static int wrpg_lcg(void) {
    lcg = (lcg * 214013 + 2531011) & 0xFFFFFFFF;
    return (lcg >> 16) & 0x7FFF;
}

/**
 * Decrypt a WolfRPG PRO dat file
 */
static void decryptdat(uint8_t *buf, uint64_t size, int o1, int o2, int o3)
{
    uint64_t i, d[3] = { 1, 2, 5 };
    int j;

    if(!buf || size < 10) return;
    for(j = 0; j < 3; j++) {
        lcg = buf[!j ? o1 : (j == 1 ? o2 : o3)];
        for(i = 10; i < size; i += d[j])
            buf[i] ^= (uint8_t)(wrpg_lcg() >> 12);
    }
}

/**
 * Get game title and tile size
 */
static void parse_game_dat(uint8_t *buf, uint64_t size)
{
    uint32_t len;
    uint8_t *ptr = NULL;

    if(!buf || size < 16) return;

    if(!memcmp(buf, dat_magic, sizeof(dat_magic)))
        /* for simple WolfRPG, magic matches */
        ptr = buf + sizeof(dat_magic) + 4;
    else if(DX8_keys[0][0]) {
        /* otherwise if we have a WolfRPG PRO key, try to decrypt */
        decryptdat(buf, size, 0, 8, 6);
        ptr = buf + *((uint32_t*)(buf + 15)) + 19;
    }

    if(ptr && ptr + 16 < buf + size) {
        tilesize = ptr[4];
        if(verbose > 1) debug("Game.dat (%lu bytes, tilewidth %u, tileheight %u)", size, tilesize, tilesize);
        if(tilesize != 16 && tilesize != 32 && tilesize != 40 && tilesize != 48 && tilesize != 64)
            tilesize = 16;
        ptr += *((uint32_t*)ptr) + 8;
        if(ptr < buf + size) {
            len = *((uint32_t*)ptr); ptr += 4;
            if(len < 255 && ptr + len < buf + size) {
                memcpy(engine_game, ptr, len);
                engine_game[len] = 0;
            }
        }
    }
}

/**
 * Read tile sets
 */
static void parse_tilesets(uint8_t *buf, uint64_t size)
{
    uint8_t ver;
    uint32_t j, l;
    uint8_t *end = buf + size, *ptr = NULL;
    int i;

    if(!buf || size < 15) return;

    if(!memcmp(buf, dat_magic, sizeof(dat_magic)))
        /* for simple WolfRPG, magic matches */
        ptr = buf + sizeof(dat_magic) + 4;
    else if(DX8_keys[0][0]) {
        /* otherwise if we have a WolfRPG PRO key, try to decrypt */
        decryptdat(buf, size, 0, 3, 9);
        ptr = buf + *((uint32_t*)(buf + 15)) + 19;
    }

    if(ptr && ptr + 15 < end) {
        /* format version, either 209 or 210 */
        ver = *ptr++;
        (void)ver;
        /* get number of tile sets. should be 100 by default */
        numtilesets = *((uint32_t*)ptr); ptr += 4;
        if(verbose > 1) debug("TileSetsData.dat (%lu bytes, %u sets)", size, numtilesets);
        if(numtilesets < 1 || numtilesets > 1024) {
            debug("invalid number of tile sets %u", numtilesets);
            numtilesets = 0;
            return;
        }
        if(!(tilesets = (int*)malloc(numtilesets * MAXAUTOTILES * 4))) {
            engine_features |= EF_NOMEM;
            debug("unable to allocate memory for tilesets (%lu bytes)", numtilesets * MAXAUTOTILES * 4);
            numtilesets = 0;
            return;
        }
        if(!(tsnames = (char*)malloc(numtilesets * MAXAUTOTILES * NAMELEN))) {
            free(tilesets); tilesets = NULL;
            engine_features |= EF_NOMEM;
            debug("unable to allocate memory for tileset names (%lu bytes)", numtilesets * MAXAUTOTILES * NAMELEN);
            numtilesets = 0;
            return;
        }
        memset(tilesets, 0xff, numtilesets * MAXAUTOTILES * 4);
        memset(tsnames, 0, numtilesets * MAXAUTOTILES * NAMELEN);

        for(i = 0; !quit && i < numtilesets && ptr < end; i++) {
            /* skip title (enoding is SHIFTJIS in 209 and UTF-8 in 210, but we skip it anyway) */
            l = *((uint32_t*)ptr); ptr += 4 + l;
            /* base tile sheet and auto tile sheets. Typical number is 16 in 209 and 32 in 210 but we simply check for separator */
            for(j = 0; ptr < end && *ptr != 0xff; j++) {
                l = *((uint32_t*)ptr); ptr += 4;
                if(j < MAXAUTOTILES)
                    memcpy(tsnames + ((i * MAXAUTOTILES + j) * NAMELEN), ptr, l > NAMELEN - 1 ? NAMELEN - 1 : l);
                ptr += l;
            }
            if(ptr >= end || *ptr != 0xff) break; else ptr++;
            /* skip tags */
            l = *((uint32_t*)ptr); ptr += 4 + l;
            if(ptr >= end || *ptr != 0xff) break; else ptr++;
            /* skip tile flags */
            l = *((uint32_t*)ptr); ptr += 4 + l * 4;
        }
    }
}

/**
 * Read in one map
 */
static void parse_maps(char *path, uint8_t *buf, uint64_t size)
{
    uint32_t tid, width, height, numevt, l, x, y, i;
    uint8_t *end = buf + size, *ptr = buf + sizeof(mps_magic) + 8, ver;
    char name[NAMELEN], *ext;
    int *map;

    if(quit || !buf || size < 21) return;

    /* format version, either 0x65 (v2) or 0x66 (v3) */
    ver = *ptr++;
    /* get title (encoding is SHIFTJIS if version is 0x65, UTF-8 otherwise) */
    l = *((uint32_t*)ptr); ptr += 4;
    if(ptr + l + 16 >= end) return;
    /* we skip this, because unfortunately most games just has "なし" here for all the maps */
    memset(name, 0, sizeof(name));
    (void)ver;
/*  if(ver == 0x65) shiftjis2utf8((char*)ptr, name, l);
    else memcpy(name, ptr, l > NAMELEN - 1 ? NAMELEN - 1 : l); */
    if(!(ext = strrchr(path, '.'))) ext = path + strlen(path);
    memcpy(name, path, (uintptr_t)ext - (uintptr_t)path);
    ptr += l;
    /* header */
    tid = *((uint32_t*)ptr); ptr += 4;
    width = *((uint32_t*)ptr); ptr += 4;
    height = *((uint32_t*)ptr); ptr += 4;
    numevt = *((uint32_t*)ptr); ptr += 4;
    if(verbose > 1) debug("map '%s' (%lu bytes, tileset %u, width %u, height %u)", path, size, tid, width, height);
    /* tiles */
    if(*((uint32_t*)ptr) != 0xffffffff && ptr + width * height * 3 * 4 < end &&
        (map = map_data(map_add(nummaps + 1, name, tid, tilesize, tilesize, width, height, 3), 0)))
            for(l = i = 0; l < 3; l++)
                for(y = 0; y < height; y++)
                    for(x = 0; x < width; x++, i++)
                        map[i] = ((uint32_t*)ptr)[l * width * height + x * height + y];
    ptr += width * height * 3 * 4;
    /* we don't parse the events */
    (void)numevt;
}

/**
 * Detect WolfRPG Pro encryption key
 */
void wrpg_detectprokey(uint8_t *buf, uint64_t size)
{
    uint64_t i, s, o;

    memset(DX8_keys[0], 0, DXA_KEY_STRING_LENGTH + 1);
    if(!buf || size < 0x5CB8 || !buf[19]) return;
    lcg = buf[4];
    for(i = 20; i < size; i++)
        buf[i] ^= (uint8_t)(wrpg_lcg() >> 12);

    for(s = 0x58B1 / buf[19], o = 30, i = 0; i < buf[19] && i < DXA_KEY_STRING_LENGTH && o < size; i++, o += s)
        DX8_keys[0][i] = buf[o];
    DX8_keys[0][i] = 0;
}

/**
 * Callback function for find() in wrpg_detect()
 */
int wrpg_detectcb(char *path)
{
    uint8_t tmp[64];
    uint64_t size = 0;
    void *f = fileopen(path, &size);

    if(f) {
        fileread(f, tmp, sizeof(tmp));
        fileclose(f);
        if(size > 64 && dxlib_detect(tmp, size)) {
            detected = 1;
            return 0;
        }
    }
    return 1;
}

/**
 * Detect if this is a WolfRPG game
 */
int wrpg_detect(char *path, uint8_t *buf, uint64_t size)
{
    char tmp[PATH_MAX + FILENAME_MAX + 1], *fn;

    if(!path || !*path) return 0;
    if(buf && size > 64 && dxlib_detect(buf, size)) return 1;

    strcpy(tmp, path);
    fn = tmp + strlen(tmp);
    strcpy(fn, "/Data/BasicData/Game.dat");
    if(fileexists(tmp)) return 1;
    *fn = 0;

    detected = 0;
    find(tmp, 1, wrpg_detectcb);
    return detected;
}

/**
 * Callback function for find() in wrpg_load()
 */
int wrpg_loadcb(char *path)
{
    char tmp[1024], *fn;
    uint8_t *buf;
    uint64_t size = 0, subtotal = 0;
    void *f = fileopen(path, &size);
    int i, l, s = 0;

    if(f) {
        fileread(f, tmp, sizeof(tmp));
        if((fn = strrchr(path, '/')) || (fn = strrchr(path, '\\'))) fn++; else fn = path;
        if(!memcmp(path + pathlen, "Data/", 5) || !memcmp(path + pathlen, "data/", 5)) s = 5; else
        if(!memcmp(path + pathlen, "/Data/", 6) || !memcmp(path + pathlen, "/data/", 6)) s = 6;
        if(size > 64 && dxlib_detect((uint8_t*)tmp, size)) {
            debug("getfile '%s' %lu bytes", path, size);
            if(engine_keylen) {
                /* if user provided a key, use that */
                engine_key[engine_keylen] = 0;
                if(!getfileindex(path, f, size, engine_key))
                    debug("user provided key doesn't work on this archive '%s'", path);
            } else {
                /* otherwise iterate on the known keys until one succeeds */
                for(i = !DX8_keys[0][0]; i < (int)(sizeof(DX8_keys)/sizeof(DX8_keys[0])) &&
                    !getfileindex(path, f, size, DX8_keys[i]); i++);
                if(i >= (int)(sizeof(DX8_keys)/sizeof(DX8_keys[0])))
                    debug("detected key doesn't work on this archive '%s'", path);
            }
            if(numidx > 0) {
                detected = 1;
                if(KeyStringBytes > 0) {
                    l = sprintf(tmp, "encryption key ");
                    for(i = 0; i < (int)KeyStringBytes; i++)
                        l += sprintf(tmp + l, " %02X", KeyString[i]);
                    debug(tmp);
                }
                l = sprintf(tmp, "DX version %u, numfiles %4u", Head.Version, numidx);
                if(KeyLen > 0) {
                    l += sprintf(tmp + l, ", xormask");
                    for(i = 0; i < (int)KeyLen; i++)
                        l += sprintf(tmp + l, " %02X", Key[i]);
                    debug(tmp);
                }
                for(i = 0; !quit && i < numidx; i++) {
                    if(idx[i].size > 64 && (buf = FileDecode(f, &idx[i]))) {
                        if((fn = strrchr(idx[i].name, '/'))) fn++; else fn = idx[i].name;
                        if(!strcmp(fn, "Game.dat")) {
                            parse_game_dat(buf, idx[i].size);
                            free(buf);
                        } else
                        if(!strcmp(fn, "TileSetData.dat")) {
                            parse_tilesets(buf, idx[i].size);
                            free(buf);
                        } else
                        if(!memcmp(buf, mps_magic, sizeof(mps_magic))) {
                            parse_maps(fn, buf, idx[i].size);
                            free(buf);
                        } else
                        if(!fileasset(-1, idx[i].name, buf, idx[i].size)) free(buf);
                    }
                    subtotal += idx[i].size;
                    status(LOADING, current + subtotal, total);
                }
            }
            freefileindex();
        } else
        if(size > 64 && !strcmp(fn, "Game.dat") && (buf = getfile(path, &size))) {
            parse_game_dat(buf, size);
            free(buf);
        } else
        if(size > 64 && !strcmp(fn, "TileSetData.dat") && (buf = getfile(path, &size))) {
            parse_tilesets(buf, size);
            free(buf);
        } else
        if(size > 64 && !memcmp(tmp, mps_magic, sizeof(mps_magic)) && (buf = getfile(path, &size))) {
            parse_maps(fn, buf, size);
            free(buf);
        } else
        if(size > 64 && fileknown((uint8_t*)tmp) && (buf = getfile(path, &size))) {
            if(!fileasset(-1, path + pathlen + s, buf, size)) free(buf);
        }
        fileclose(f);
    }
    current += size;
    status(LOADING, current, total);
    return 1;
}

/**
 * Load all assets from a WolfRPG game
 */
void wrpg_load(char *path, int isdir)
{
    uint8_t *buf;
    uint64_t size;
    char tmp[PATH_MAX + FILENAME_MAX + 1];
    char *fn;
    int i;

    (void)isdir;
    if(!path || !*path) return;

    status(LOADING, 0, 100);
    fn = strrchr(path, '/');
    if(!fn) fn = strrchr(path, '\\');
    if(fn) strncpy(engine_game, fn + 1, sizeof(engine_game) - 1);
    else strcpy(engine_game, "?");

    detected = 0; DX8_keys[0][0] = 0;
    tilesize = 16;
    strcpy(tmp, path);
    pathlen = strlen(tmp);
    strcpy(tmp + pathlen, "/Data/Game.wolf");
    if(!engine_keylen && (buf = getfile(tmp, &size))) {
        wrpg_detectprokey(buf, size);
        free(buf);
    }
    tmp[pathlen] = 0;
    current = 0; total = du(tmp);
    find(tmp, 1, wrpg_loadcb);
    if(!detected) debug("failed to retrieve encryption key, press F2 and specify manually");

    if(tilesets && tsnames) {
        if(numtilesets > 1024) numtilesets = 1024;
        for(i = 0; !quit && i < numtilesets * MAXAUTOTILES; i++) {
            tilesets[i] = sprites_addsheet(-1, tsnames + i * NAMELEN, NULL, 0);
            if(verbose > 1 && tsnames[i * NAMELEN])
                debug("tileset %d %d '%s' sheet %d", i / MAXAUTOTILES, i % MAXAUTOTILES, tsnames + i * NAMELEN, tilesets[i]);
        }
        free(tsnames); tsnames = NULL;
    }
    /* we might have parsed Game.dat after the maps */
    if(maps)
        for(i = 0; i < nummaps; i++)
            maps[i].tw = maps[i].th = tilesize;

    engine_features |= EF_DECRYPT;
}

/**
 * Free any additional resources that wrpg_load() allocated
 */
void wrpg_unload(void)
{
    freefileindex();
    if(tsnames) { free(tsnames); tsnames = NULL; }
    if(tilesets) { free(tilesets); tilesets = NULL; }
}

/**
 * Callback function for find() in wrpg_decrypt()
 */
int wrpg_unpackcb(char *path)
{
    char *fn, *s;
    uint8_t tmp[64], *buf;
    uint64_t size = 0, subtotal = 0;
    void *f = fileopen(path, &size);
    int i;

    if(f) {
        fileread(f, tmp, sizeof(tmp));
        if(size > 64 && dxlib_detect(tmp, size)) {
            debug("getfile '%s' %lu bytes", path, size);
            if(engine_keylen) {
                /* if user provided a key, use that */
                engine_key[engine_keylen] = 0;
                getfileindex(path, f, size, engine_key);
            } else {
                /* otherwise iterate on the known keys until one succeeds */
                for(i = !DX8_keys[0][0]; i < (int)(sizeof(DX8_keys)/sizeof(DX8_keys[0])) &&
                    !getfileindex(path, f, size, DX8_keys[i]); i++);
            }
            if(numidx > 0) {
                if((fn = strrchr(path, '/')) || (fn = strrchr(path, '\\'))) fn++;
                else fn = path;
                for(i = 0; !quit && i < numidx; i++) {
                    if((buf = FileDecode(f, &idx[i]))) {
                        strcpy(fn, idx[i].name);
                        for(s = fn; *s; s++)
                            if(*s == '/') { *s = 0; filemkdir(path); *s = '/'; }
                        putfile(path, buf, idx[i].size, NULL, 0, NULL, 0);
                        free(buf);
                    }
                    subtotal += idx[i].size;
                    status(SAVING, current + subtotal, total);
                }
            }
            freefileindex();
        }
        fileclose(f);
    }
    current += size;
    status(SAVING, current, total);
    return 1;
}

/**
 * Decrypt and unpack all files in a WolfRPG game
 */
void wrpg_decrypt(char *path, int isdir)
{
    uint8_t *buf;
    uint64_t size;
    char tmp[PATH_MAX + FILENAME_MAX + 1];

    (void)isdir;
    if(!path || !*path) return;

    status(SAVING, 0, 100);
    strcpy(tmp, path);
    pathlen = strlen(tmp);
    strcpy(tmp + pathlen, "/Data/Game.wolf");
    if(!engine_keylen && !DX8_keys[0][0] && (buf = getfile(tmp, &size))) {
        wrpg_detectprokey(buf, size);
        free(buf);
    }
    tmp[pathlen] = 0;
    current = 0; total = du(tmp);
    find(tmp, 1, wrpg_unpackcb);
}

/**
 * Draw map tiles to off-screen pixel buffer
 */
void wrpg_drawmap(map_t *map)
{
    int i = 0, x, y, dx, dy, l, tile, a, s, sc, tw, th;
    int top_left, top_right, bottom_left, bottom_right;

    if(!map || !map->tw || !map->th || map->tid < 0 || map->tid >= numtilesets || tilesets[map->tid * MAXAUTOTILES] == -1) return;
    s = tilesets[map->tid * MAXAUTOTILES];
    sc = sheets[s].w / map->tw;
    tw = map->tw / 2; th = map->th / 2;
    for(l = i = 0; l < map->l; l++)
        for(y = dy = 0; y < map->h; y++, dy += map->th)
            for(x = dx = 0; x < map->w; x++, dx += map->tw) {
                tile = map->data[i++];
                if(tile / 100000) {
                    /* autotile */
                    top_left = tile % 10000 / 1000;
                    top_right = tile % 1000 / 100;
                    bottom_left = tile % 100 / 10;
                    bottom_right = tile % 10;
                    tile /= 100000; tile--;
                    a = tilesets[map->tid * MAXAUTOTILES + tile];
                    if(tile > 0 && tile < MAXAUTOTILES && a != -1) {
                        map_tile(a, 0, (top_left * map->th) % sheets[a].h, tw, th, dx, dy);
                        map_tile(a, tw, (top_right * map->th) % sheets[a].h, tw, th, dx + tw, dy);
                        map_tile(a, 0, (th + bottom_left * map->th) % sheets[a].h, tw, th, dx, dy + th);
                        map_tile(a, tw, (th + bottom_right * map->th) % sheets[a].h, tw, th, dx + tw, dy + th);
                    }
                } else {
                    /* base tile */
                    map_tile(s, (tile % sc) * map->tw, (tile / sc) * map->th, map->tw, map->th, dx, dy);
                }
            }
}

/**
 * Save translated text
 */
void wrpg_translate(char *path)
{
    msgbox(0, UNIMPLEMENTED, path);
}

ENGINE_MODULE(wrpg, "Wolf RPG DX6 / DX8 Archive")
